package com.demo.function.filter.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

@Slf4j
public abstract class AbstractRequestWrapper extends HttpServletRequestWrapper {

    private final String method;

    private byte[] streamByteArray;

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public AbstractRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        this.method = request.getMethod();
    }

    public boolean notPass() throws IOException {
        if (StringUtils.equalsIgnoreCase("get", method)) {
            return checkGetMethodKeyWords();
        } else if (StringUtils.equalsIgnoreCase("post", method)
                && !ServletFileUpload.isMultipartContent(this)) { // post请求上传文件不校验
            return checkPostMethodKeyWords();
        }
        return false;
    }

    /**
     * 校验Get请求参数
     * @return 返回校验结果，true-有敏感字符，false-没有敏感字符
     */
    private boolean checkGetMethodKeyWords() {
        Map<String, String[]> parameterMap = super.getParameterMap();
        if (MapUtils.isEmpty(parameterMap)) {
            return false;
        }

        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            for (String value : entry.getValue()) {
                if (StringUtils.isBlank(value)) {
                    continue;
                }

                boolean flag = validateKeyWords(value);
                if (flag) {
                    log.warn(getErrorMsg(value));
                    return flag;
                }
            }
        }

        return false;
    }

    /**
     * 校验Post请求参数
     * @return 返回校验结果，true-有敏感字符，false-没有敏感字符
     * @throws IOException 异常
     */
    private boolean checkPostMethodKeyWords() throws IOException {
        // 参数转换成JSON
        Object obj = convertJSON();
        if (obj instanceof JSONArray) {
            JSONArray array = (JSONArray) obj;
            for (Object o : array) {
                boolean flag;
                if (o instanceof String) {
                    flag = validateKeyWords((String) o);
                } else if (o instanceof  JSONObject) {
                    flag = validateValue((JSONObject) o);
                } else {
                    log.info("其他类型：" + o) ;
                    return false;
                }

                if (flag) {
                    return flag;
                }
            }
            return false;
        } else {
            return validateValue((JSONObject) obj);
        }
    }

    /**
     * 参数转换成JSON
     * 注意：表单请求和body流请求获取参数方式不同，需要区别对待
     * @return 返回JSON对象，有两种对象，一种是JSON对象，一种是数组JSON对象
     * @throws IOException 异常
     */
    private Object convertJSON() throws IOException {
        if (isMatchContentType(MediaType.APPLICATION_JSON_VALUE)) {
            byte[] bytes = IOUtils.toByteArray(this.getReader(), StandardCharsets.UTF_8);
            if (ArrayUtils.isEmpty(bytes)) {
                return new JSONObject();
            }

            String content = new String(bytes);
            if (StringUtils.startsWith(content, "[")) {
                return JSON.parseArray(content);
            } else {
                return JSON.parseObject(content);
            }
        } else if (isMatchContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            Map<String, String[]> map = super.getParameterMap();
            JSONObject jsonObject = new JSONObject();

            for (Map.Entry<String, String[]> entry : map.entrySet()) {
                String value = String.join(",", entry.getValue());
                jsonObject.put(entry.getKey(), value);
            }
            return jsonObject;
        }
        return new JSONObject();
    }

    private boolean isMatchContentType(String contentType) {
        return StringUtils.equalsIgnoreCase(this.getContentType(), contentType);
    }

    private boolean validateValue(JSONObject jsonObject) {
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            Object value = entry.getValue();
            if (Objects.isNull(value)) {
                continue;
            }

            boolean flag = validateKeyWords(value.toString());
            if (flag) {
                log.warn(getErrorMsg(value.toString()));
                return flag;
            }
        }
        return false;
    }

    protected abstract boolean validateKeyWords(String value);

    protected abstract String getErrorMsg(String value);

    /**
     * 重载方法，解决流只能读一次的问题
     * @return 返回BufferedReader对象
     * @throws IOException 异常
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (ArrayUtils.isEmpty(streamByteArray)) {
            streamByteArray = IOUtils.toByteArray(super.getReader(), StandardCharsets.UTF_8);
        }

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(streamByteArray);
        return new ServletInputStream() {

            @Override
            public int read() {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }
        };
    }
}
